#include <queue>

#include <boost/bind.hpp>
#include <boost/random.hpp>

#include "StartFacade.h"

#include "TcpTunnelBuilder.h"
#include "XORCryptoFactory.h"
#include "BandwithLimitSendProxy.h"

#include <iostream>

namespace encrypted
{
    namespace tunnel
    {

        // Convert from char to ascii char for hex
        // Ex: input from = 0x6d, output hi = '6' and lo = 'd'
        void tohex(unsigned char& hi, unsigned char& lo, unsigned char from)
        {
            hi = from >> 4;
            if(hi > 9)
                hi += 'a' - 10;
            else
                hi += '0';
            lo = from & 0x0f;
            if(lo > 9)
                lo += 'a' - 10;
            else
                lo += '0';
        }

        // Convert hi and low from hex to char
        // Ex: input hi = 'c' and lo = '3', return is 0xc3
        unsigned char fromhex(unsigned char hi, unsigned char lo)
        {
            if(hi >= 'a')
                hi -= 'a' - 10;
            else
                hi -= '0';
            if(lo >= 'a')
                lo -= 'a' - 10;
            else
                lo -= '0';
            return hi << 4 | lo;
        }

        SecretKeyGeneratedListener::~SecretKeyGeneratedListener()
        {}

        StartFacade::StartFacade()
        {
        }

        StartFacade::~StartFacade()
        {
        }

        int StartFacade::addService(std::string listenPort,
                                    std::string connectPort,
                                    std::string connectIP,
                                    StartMode mode,
                                    std::string secretKey,
                                    std::string bindHost,
                                    unsigned int randSeed,
                                    unsigned int bandWidth)
        {
            typedef XORCryptoFactory<boost::shared_array<unsigned char> > CryptoFactory;
            typedef boost::shared_ptr<CryptoFactory> CryptoFactoryPtr;
            typedef CryptoFactory::DecryptorPtr CryptoPtr;
            typedef BandwithLimitSendProxy<CryptoPtr> SendProxy;
            typedef boost::shared_ptr<SendProxy> SendProxyPtr;

            const size_t keysize = 32;

            // Construct a secret key buffer
            boost::shared_array<unsigned char> key(new unsigned char[keysize]);

            // If mode == 1 means it's server, then generate the secret key
            if( mode == ServerMode )
            {
                // Random Seed equals zero is not safe when running mode is server.  Please pass the real random seed to the start function.
                // The randSeed should be generated by the random number generator of system.
                // Just use the current time as the seed is not very safe.

                if(randSeed == 0)
                    randSeed = time(NULL);
                boost::mt19937 rnd;
                rnd.seed(randSeed);
                boost::variate_generator<boost::mt19937&,
                    boost::uniform_smallint<> > randomizer(rnd, boost::uniform_smallint<>(0, 0x0ff));
                std::cout << "INFO:Generated Secret Key is:" << std::endl;
                //std::cout << "INFO:";
                unsigned char hi, lo;
                secretKey = "";
                for(size_t i = 0; i < keysize; ++i)
                {
                    key[i] = randomizer();
                    tohex(hi, lo, key[i]);
                    //std::cout << hi << lo;
                    secretKey += hi;
                    secretKey += lo;
                }
                //std::cout << std::endl;
                for(SecretKeyGeneratedListeners::iterator it = secretKeyGeneratedListeners.begin(); it != secretKeyGeneratedListeners.end(); ++it)
                    (*it)->secretKeyGenerated(secretKey);
                std::cout << "INFO:Please send the secret key above to your users." << std::endl;
            }

            // If mode != 1 means it's client, then use the secret_key as the secret key
            else
            {
                if ( secretKey == "" )
                {
                    std::cerr<< "FATAL:No Serect Key" << std::endl;
                    exit(1);
                };
                for(size_t i = 0; secretKey[i] != '\0' && secretKey[i + 1] != '\0'; i += 2)
                    key[i / 2] = fromhex(secretKey[i], secretKey[i + 1]);
            }
            if(bindHost == "")
                bindHost = boost::asio::ip::host_name();
            try
            {
                SendProxyPtr sendProxy(new SendProxy(io_service, bandWidth));
                sendProxy->start();
                for(boost::asio::ip::tcp::resolver::iterator it = boost::asio::ip::tcp::resolver(io_service).resolve(boost::asio::ip::tcp::resolver_query(bindHost, listenPort)), end; it != end; ++it)
                    (new TcpTunnelBuilder<CryptoFactoryPtr, SendProxyPtr>(io_service, it->endpoint(), connectIP, connectPort, CryptoFactoryPtr(new CryptoFactory(key, keysize)), sendProxy))->start();
            }
            catch (const std::exception& e)
            {
                std::cerr << "FATAL:" << e.what() << std::endl;
                return -1;
            }
            return 0;
        }

        int StartFacade::start()
        {
            try
            {
                io_service.run();
            }
            catch (const std::exception& e)
            {
                std::cerr << "FATAL:" << e.what() << std::endl;
                return -1;
            }
            return 0;
        }

        void StartFacade::addSecretKeyGeneratedListener(boost::shared_ptr<SecretKeyGeneratedListener> listener)
        {
            secretKeyGeneratedListeners.insert(listener);
        }

    }
}
